package com.bcx.wind.workflow.core.flow.node;

import com.bcx.wind.workflow.core.constant.NodeType;
import com.bcx.wind.workflow.core.flow.Model;
import com.bcx.wind.workflow.core.flow.NodeModel;
import com.bcx.wind.workflow.core.flow.TaskModel;
import com.bcx.wind.workflow.core.flow.check.AndCache;
import com.bcx.wind.workflow.core.flow.check.DynaCache;
import com.bcx.wind.workflow.core.flow.check.NodeCache;
import com.bcx.wind.workflow.core.flow.check.OrCache;
import com.bcx.wind.workflow.exception.WorkflowException;
import com.bcx.wind.workflow.handler.Handler;
import com.bcx.wind.workflow.handler.ScribeTaskHandler;
import com.bcx.wind.workflow.handler.TaskHandler;
import com.bcx.wind.workflow.interceptor.CommandContext;
import com.bcx.wind.workflow.interceptor.Context;
import com.bcx.wind.workflow.pojo.Task;
import com.bcx.wind.workflow.pojo.Wind;
import com.bcx.wind.workflow.pojo.WindConfig;
import com.bcx.wind.workflow.pojo.WindUser;
import com.bcx.wind.workflow.support.Assert;
import com.bcx.wind.workflow.support.ObjectHelper;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static com.bcx.wind.workflow.core.constant.NodeNameConstant.*;
import static com.bcx.wind.workflow.core.constant.NodeProConstant.*;
import static com.bcx.wind.workflow.message.ErrorCode.PATH_NO_FOUND_NODE;

/**
 * 节点处理基类
 *
 * @author zhanglei
 */
public abstract class BaseNode extends AbstractNode implements NodeModel,Serializable {


    /**
     * 流程模型
     */
    protected Element  element;

    /**
     * 当前模型
     */
    protected Element  now;

    /**
     * 节点类型
     */
    protected NodeType nodeType;


    /**
     * 父节点
     */
    protected NodeModel  parentNode;

    /**
     * 是否在子流程中
     */
    private boolean inRouter;

    /**
     * 路线
     */
    protected List<Path>  paths = new LinkedList<>();

    /**
     * 后续节点
     */
    protected List<NodeModel> nextNode = new LinkedList<>();


    /**
     * 后续任务节点
     */
    protected List<NodeModel> nextTask = new LinkedList<>();


    /**
     * 前面节点
     */
    protected List<NodeModel> preNode = new LinkedList<>();

    /**
     * 前面任务节点
     */
    protected List<NodeModel> preTask = new LinkedList<>();


    public BaseNode(String nodeId,String nodeName){
        this.setNodeId(nodeId);
        this.setNodeName(nodeName);
    }

    /**
     * 执行
     */
    public abstract void executor();


    @Override
    public void next(String line){
        List<NodeModel> nextNode = next();
        if(line != null){
            nextNode = submitLineTask(line);
            Assert.notEmptyError(PATH_NO_FOUND_NODE,nextNode,line);
        }
        for(NodeModel next : nextNode){
            if(next instanceof TaskNode){
                this.handler(new TaskHandler(this.commandContext(),next,this.task()));
            }else if(next instanceof ScribeTaskNode){
                this.handler(new ScribeTaskHandler(this.commandContext(),next,this.task()));
            }else{
                next.context(this.commandContext(),this.task());
                next.execute();
            }
        }
    }

    @Override
    public void execute() {
        this.executor();
    }

    protected   void  handler(Handler handler){
        handler.handler();
    }

    @Override
    public void context(CommandContext commandContext,Task task) {
        Context.set(commandContext,task);
    }

    @Override
    public String nodeId() {
        return this.getNodeId();
    }

    @Override
    public String nodeName() {
        return this.getNodeName();
    }

    @Override
    public NodeType nodeType() {
        return this.nodeType;
    }

    @Override
    public NodeModel parentNode() {
        return this.parentNode;
    }

    @Override
    public List<NodeModel> next() {
        return this.nextNode;
    }

    @Override
    public List<NodeModel> pre() {
        return this.preNode;
    }

    @Override
    public List<NodeModel> nextTask() {
        return this.nextTask;
    }

    @Override
    public List<NodeModel> preTask() {
        return this.preTask;
    }

    @Override
    public List<NodeModel> submitLineTask(String line) {
        List<Path> ps = this.path();
        for(Path path : ps){
            if(line.equals(path.nodeId())){
                return Collections.singletonList(path.getNextNode());
            }
        }
        return Collections.emptyList();
    }

    @Override
    public List<NodeModel> nextAllTask() {
        List<NodeModel> next = next();
        List<NodeModel> allTasks = new LinkedList<>();
        buildAllNextTasks(allTasks,next);
        return allTasks;
    }


    private void  buildAllNextTasks(List<NodeModel> allTasks,List<NodeModel> nextNodes){
        for(NodeModel nodeModel : nextNodes){

            //如果是任务节点则直接新增
            if(nodeModel instanceof TaskNode){
                if(!allTasks.contains(nodeModel)) {
                    allTasks.add(nodeModel);
                }else{
                    break;
                }
            }

            //如果是子流程节点，则获取第一个任务节点,并将当前节点设置为子流程中的第一个任务节点
            if(nodeModel instanceof RouterNode){
                NodeModel firstNode = ((RouterNode) nodeModel).getFirstTaskNode();
                if(!allTasks.contains(firstNode)) {
                    allTasks.add(firstNode);
                }
                nodeModel = firstNode;
            }

            //如果当前节点处于子流程中，并且是结束节点，则将当前节点设置为子流程节点，为后面递归操作
            if(nodeModel.parentNode() instanceof RouterNode && nodeModel instanceof EndNode){
                nodeModel = ((EndNode) nodeModel).parentNode;
            }

            buildAllNextTasks(allTasks,nodeModel.next());
        }
    }

    @Override
    public List<Path> path() {
        return this.paths;
    }

    @Override
    public boolean inRouter() {
        return this.inRouter;
    }

    @Override
    public void inRouter(boolean inRouter) {
        this.inRouter = inRouter;
    }


    protected void setDefaultPro(Element now,Element element){
        this.now = now;
        this.element = element;
    }

    private NodeModel buildNodeModel(String nodeName,String name,String displayName,Element now){
        Model model = NodeCache.getInstance().get(name);
        if(model instanceof NodeModel){
            return (NodeModel) model;
        }
        switch (nodeName){
            case TASK:
                TaskNode taskNode =  new TaskNode(name,displayName);
                taskNode.setDefaultPro(now,element);
                taskNode.setParentNode(this.parentNode);
                if(this instanceof AndNode) {
                    forkCacheChange(this.getNodeId(), 1);
                }
                if(this instanceof OrNode){
                    forkCacheChange(this.getNodeId(),2);
                }
                if(this instanceof DynaNode){
                    forkCacheChange(this.getNodeId(),3);
                }

                if(this instanceof TaskNode){
                    if(((TaskNode) this).inAnd()) {
                        AndCache.getInstance().inAnd(1);
                    }
                    if(((TaskNode) this).inOr()){
                        OrCache.getInstance().inOr(1);
                    }
                }

                taskNode.setInAnd(AndCache.getInstance().inAnd());
                taskNode.setInOr(OrCache.getInstance().inOr());
                taskNode.setInDyna(DynaCache.getInstance().inDyna());
                taskNode.setInForkJoin(AndCache.getInstance().inAnd() || OrCache.getInstance().inOr() || DynaCache.getInstance().inDyna());

                NodeCache.getInstance().put(name,taskNode);
                taskNode.build();
                return taskNode;
            case END:
                EndNode endNode =  new EndNode(name,displayName);
                endNode.setDefaultPro(now,element);
                endNode.setParentNode(parentNode);
                endNode.build();
                NodeCache.getInstance().put(name,endNode);
                return endNode;
            case AND:
                AndNode andNode =  new AndNode(name,displayName);
                andNode.setDefaultPro(now,element);
                andNode.setParentNode(parentNode);
                andNode.build();
                NodeCache.getInstance().put(name,andNode);
                return andNode;
            case OR:
                OrNode orNode =  new OrNode(name,displayName);
                orNode.setDefaultPro(now,element);
                orNode.setParentNode(parentNode);
                orNode.build();
                NodeCache.getInstance().put(name,orNode);
                return orNode;
            case DYNA:
                DynaNode dynaNode = new DynaNode(name,displayName);
                dynaNode.setDefaultPro(now,element);
                dynaNode.setParentNode(parentNode);
                dynaNode.build();
                NodeCache.getInstance().put(name,dynaNode);
                return dynaNode;
            case AND_JOIN:
                AndJoinNode andJoinNode =  new AndJoinNode(name,displayName);
                andJoinNode.setDefaultPro(now,element);
                andJoinNode.setParentNode(parentNode);
                joinCacheChange(name,1);
                andJoinNode.build();
                NodeCache.getInstance().put(name,andJoinNode);
                return andJoinNode;
            case OR_JOIN:
                OrJoinNode orJoinNode =  new OrJoinNode(name,displayName);
                orJoinNode.setDefaultPro(now,element);
                orJoinNode.setParentNode(parentNode);
                joinCacheChange(name,2);
                orJoinNode.build();
                NodeCache.getInstance().put(name,orJoinNode);
                return orJoinNode;
            case DYNA_JOIN:
                DynaJoinNode dynaJoinNode = new DynaJoinNode(name,displayName);
                dynaJoinNode.setDefaultPro(now,element);
                dynaJoinNode.setParentNode(parentNode);
                joinCacheChange(name,3);
                dynaJoinNode.build();
                NodeCache.getInstance().put(name,dynaJoinNode);
                return dynaJoinNode;
            case ROUTER:
                RouterNode routerNode =  new RouterNode(name,displayName);
                routerNode.setDefaultPro(now,element);
                routerNode.setParentNode(parentNode);
                routerNode.build();
                NodeCache.getInstance().put(name,routerNode);
                return routerNode;
            case DISC:
                DiscNode discNode =  new DiscNode(name,displayName);
                discNode.setDefaultPro(now,element);
                discNode.setParentNode(parentNode);
                discNode.build();
                NodeCache.getInstance().put(name,discNode);
                return discNode;
            case SCRIBE:
                ScribeNode scribeNode = new ScribeNode(name,displayName);
                scribeNode.setDefaultPro(now,element);
                scribeNode.setParentNode(parentNode);
                scribeNode.build();
                NodeCache.getInstance().put(name,scribeNode);
                return scribeNode;
            case SCRIBE_TASK:
                ScribeTaskNode scribeTaskNode = new ScribeTaskNode(name,displayName);
                scribeTaskNode.setDefaultPro(now,element);
                scribeTaskNode.setParentNode(parentNode);
                scribeTaskNode.build();
                NodeCache.getInstance().put(name,scribeTaskNode);
                return scribeTaskNode;
            case CUSTOM:
                Custom customNode = new Custom(name,displayName);
                customNode.setDefaultPro(now,element);
                customNode.setParentNode(parentNode);
                customNode.build();
                NodeCache.getInstance().put(name,customNode);
                return customNode;
            default:
                throw new WorkflowException("nodeName:"+nodeName+" , is not found, please update process!");

        }
    }




    /**'
     *构建提交路线
     * @param now  当前节点
     */
    protected void buildPaths(Element now) {
        NodeList ps = now.getChildNodes();
        for (int i = 0; i < ps.getLength(); i++) {
            org.w3c.dom.Node node = ps.item(i);
            if (node instanceof Element && PATH.equals(node.getNodeName())) {
                String to = ((Element) node).getAttribute(TO);
                Assert.notEmpty("path node is lack 'to' property! build process fail!", to);

                String name = ((Element) node).getAttribute(NAME);
                String displayName = ((Element) node).getAttribute(DISPLAY_NAME);
                String expr = ((Element) node).getAttribute(EXPR);
                Path path = new Path(name, displayName);

                NodeCache.getInstance().put(name,path);
                path.setTo(to);
                path.setExpr(expr);
                path.setPreNode(this);
                //校验路线不可指向自己
                pathToSameNowNode(to,this.nodeId(),name);

                //查询后续节点
                NodeList childNodes = element.getChildNodes();
                boolean exists = false;
                for (int j = 0; j < childNodes.getLength(); j++) {
                    org.w3c.dom.Node child = childNodes.item(j);

                    if (child instanceof Element) {
                        Element next = (Element) child;
                        String nodeName = next.getAttribute(NAME);

                        if (to.equals(nodeName)) {
                            exists = true;
                            String type = next.getNodeName();
                            String nextNodeName = next.getAttribute(NAME);
                            String nextDisplayName = next.getAttribute(DISPLAY_NAME);
                            //判断查询的路线后的节点是否是循环路线
                            NodeModel nodeModel = null;
                            Model model = NodeCache.getInstance().get(to);
                            if(model instanceof NodeModel && this instanceof DiscNode){
                               nodeModel = (NodeModel) model;
                            }else {
                                nodeModel = buildNodeModel(type, nextNodeName, nextDisplayName, next);
                            }
                            //添加到缓存
                            nodeModel.pre().add(this);
                            nodeModel.inRouter(ROUTER.equals(this.parentNode.nodeType().value()));

                            if (TASK.equals(nodeModel.nodeType().value())) {
                                TaskNode task = pathToTask(nodeModel,next,childNodes,child);
                                path.setNextTaskNode(task);
                            }else if(SCRIBE_TASK.equals(nodeModel.nodeType().value())){
                                String  interceptor = next.getAttribute(INTERCEPTOR);
                                ((ScribeTaskNode)nodeModel).setInterceptor(!ObjectHelper.isEmpty(interceptor) && TRUE.equals(interceptor));
                                path.setNextTaskNode((ScribeTaskNode)nodeModel);
                            }else if(CUSTOM.equals(nodeModel.nodeType().value())){
                                String  interceptor = next.getAttribute(INTERCEPTOR);
                                ((Custom)nodeModel).setInterceptor(!ObjectHelper.isEmpty(interceptor) && TRUE.equals(interceptor));
                            }else if(ROUTER.equals(nodeModel.nodeType().value())){
                                String block = next.getAttribute(BLOCK);
                                ((RouterNode)nodeModel).setBlock(ObjectHelper.isEmpty(block) || TRUE.equals(block));
                            }

                            path.setNextNode(nodeModel);
                        }
                    }
                }
                //校验路线无法找到指向节点
                pathToNotFound(exists,new String[]{name,to},name);
                this.paths.add(path);
            }
        }
    }



    private TaskNode  pathToTask(NodeModel nodeModel,Element next,NodeList childNodes,org.w3c.dom.Node child){
        TaskNode task = (TaskNode) nodeModel;
        //会签属性
        String  jointly = next.getAttribute(JOINTLY);
        String strand = next.getAttribute(STRAND);
        String  interceptor = next.getAttribute(INTERCEPTOR);
        String  assignee = next.getAttribute(ASSIGNEE_USER);
        task.setJointly(!ObjectHelper.isEmpty(jointly) && TRUE.equals(jointly));
        task.setStrand(!ObjectHelper.isEmpty(strand) && TRUE.equals(strand));
        task.setInterceptor(!ObjectHelper.isEmpty(interceptor) && TRUE.equals(interceptor));

        if(null != assignee && !"".equals(assignee)){
            boolean findAssignee = false;

            NodeList childs = element.getChildNodes();
            for (int k = 0; k < childs.getLength(); k++) {
                org.w3c.dom.Node c = childNodes.item(k);

                if (c instanceof Element) {
                    Element ele = (Element) c;
                    String assigneeName = ele.getNodeName();
                    if(ASSIGNEE.equals(assigneeName)){
                        String assigneeId = ele.getAttribute(ID);
                        if(assigneeId.equals(assignee)){
                            findAssignee = true;
                            NodeList propertys = ele.getChildNodes();

                            List<WindUser> users = new LinkedList<>();
                            for (int n = 0; n < propertys.getLength(); n++) {
                                org.w3c.dom.Node property = propertys.item(n);
                                if (property instanceof Element) {
                                    Element pro = (Element) property;
                                    String userId = pro.getAttribute(USER_ID);
                                    String userName = pro.getAttribute(USER_NAME);
                                    WindUser user = new WindUser();
                                    user.setUserId(userId);
                                    user.setUserName(userName);
                                    users.add(user);
                                }
                            }
                            task.setApproveUsers(users);
                        }
                    }
                }
            }
            assigneeNotFound(findAssignee,new String[]{assignee,task.nodeId()},assignee);
        }
        return task;
    }



    /**
     * 构建流程模型节点指针
     */
    protected void createNodePointer(){
        createNextNodes();
        createNextTaskNodes(this);
    }


    protected void createPreTaskNodes(NodeModel nodeModel,NodeModel act){
        List<NodeModel> lastNodes = nodeModel.pre();
        for(NodeModel node : lastNodes){

            if(node instanceof TaskNode){
                if(!act.preTask().contains(node)) {
                    act.preTask().add(node);
                }
            }else{
                createPreTaskNodes(node,act);
            }
        }
    }

    protected void createNextNodes(){
        for(Path path : paths){
            NodeModel nodeModel = path.getNextNode();

            this.nextNode.add(nodeModel);
        }
    }


    protected void createNextTaskNodes(NodeModel node){
        List<Path> ps = node.path();
        for(Path path : ps){
            TaskModel taskNode = path.getNextTaskNode();
            NodeModel nodeModel = path.getNextNode();

            //如果是任务，直接添加
            if(!ObjectHelper.isEmpty(taskNode)) {
                if(!nextTask.contains(node)) {
                    this.nextTask.add(taskNode);
                }
            }else if(!ObjectHelper.isEmpty(nodeModel)){
                //不是任务 迭代操作
                if(!TASK.equals(nodeModel.nodeType().value())){
                    createNextTaskNodes(nodeModel);
                }
            }
        }
    }

    /**
     * 根据键，获取指定节点配置
     * @param key  键
     * @return   节点配置值
     */
    protected String getConfigValue(String key){
        WindConfig config =  this.task().getConfig();
        if(config != null){
            Map<String,Object> nodeConfig = config.nodeConfig();
            Object value = nodeConfig.get(key);
            return value == null ? null : value.toString();
        }
        return null;
    }


    protected Object  getOrderValue(String key){
        Map<String,Object> variable = this.wind().getWindOrder().variable();
        return  variable.get(key);
    }

    protected Wind  wind(){
        return this.commandContext().getWind();
    }

    protected WindUser  user(){
        return wind().getUser();
    }

    public Element getElement() {
        return element;
    }

    public void setElement(Element element) {
        this.element = element;
    }

    public Element getNow() {
        return now;
    }

    public void setNow(Element now) {
        this.now = now;
    }

    public NodeType getNodeType() {
        return nodeType;
    }

    public void setNodeType(NodeType nodeType) {
        this.nodeType = nodeType;
    }

    public NodeModel getParentNode() {
        return parentNode;
    }

    public void setParentNode(NodeModel parentNode) {
        this.parentNode = parentNode;
    }

    public List<Path> getPaths() {
        return paths;
    }

    public void setPaths(List<Path> paths) {
        this.paths = paths;
    }

    public void setInRouter(boolean inRouter) {
        this.inRouter = inRouter;
    }

    protected CommandContext commandContext(){
        return Context.getCommandContext();
    }

    protected Task task(){
        return Context.getTask();
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof NodeModel){
            return this.nodeId().equals(((NodeModel) obj).nodeId());
        }
        return super.equals(obj);
    }


}
